package io.renren.modules.demo.algorithm;

import java.util.ArrayList;

public class SimulatedAnnealing
{
    private static int temperature;
    private static double coolingDownFactor;

    private static double exchangeRateBegin;

    private static double exchangeRateEnd;

    private static double reverseRateBegin;

    private static double reverseRateEnd;

    private static double insertRateBegin;

    private static double insertRateEnd;
    public static void Exchange(Chromosome chromosome)
    {
        Chromosome chromosomeCopy = new Chromosome(chromosome);
        int length = chromosomeCopy.getChromosome().size();
        int begin = (int)(Math.random() * (length - 1));
        int end = (int)(Math.random() * (length - 1 - begin)) + begin + 1;
        Gene temp = chromosomeCopy.getChromosome().get(begin);
        chromosomeCopy.getChromosome().set(begin, chromosomeCopy.getChromosome().get(end));
        chromosomeCopy.getChromosome().set(end, temp);

        chromosomeCopy.CalculateFitness();
        if(SelectNewAnswer(chromosome.getFitness(), chromosomeCopy.getFitness()))
        {
            chromosome.setChromosome(chromosomeCopy.getChromosome());
            chromosome.setFitness(chromosomeCopy.getFitness());
        }
    }

    public static void Reverse(Chromosome chromosome)
    {
        Chromosome chromosomeCopy = new Chromosome(chromosome);
        int length = chromosomeCopy.getChromosome().size();
        int begin = (int)(Math.random() * (length - 1));
        int end = (int)(Math.random() * (length - 1 - begin)) + begin + 1;
        while (begin < end)
        {
            Gene temp = chromosomeCopy.getChromosome().get(begin);
            chromosomeCopy.getChromosome().set(begin, chromosomeCopy.getChromosome().get(end));
            chromosomeCopy.getChromosome().set(end, temp);
            begin++;
            end--;
        }

        chromosomeCopy.CalculateFitness();
        if(SelectNewAnswer(chromosome.getFitness(), chromosomeCopy.getFitness()))
        {
            chromosome.setChromosome(chromosomeCopy.getChromosome());
            chromosome.setFitness(chromosomeCopy.getFitness());
        }
    }

    public static void Insert(Chromosome chromosome)
    {
        Chromosome chromosomeCopy = new Chromosome(chromosome);
        int length = chromosomeCopy.getChromosome().size();
        int begin = (int)(Math.random() * (length - 1));
        int end = (int)(Math.random() * (length - 1 - begin)) + begin + 1;
        Gene temp = chromosomeCopy.getChromosome().get(begin);
        chromosomeCopy.getChromosome().remove(begin);
        chromosomeCopy.getChromosome().add(end - 1, temp);

        chromosomeCopy.CalculateFitness();
        if(SelectNewAnswer(chromosome.getFitness(), chromosomeCopy.getFitness()))
        {
            chromosome.setChromosome(chromosomeCopy.getChromosome());
            chromosome.setFitness(chromosomeCopy.getFitness());
        }
    }

    public static boolean SelectNewAnswer(double oldChromosomeFitness, double newChromosomeFitness)
    {
        if(newChromosomeFitness > oldChromosomeFitness)
        {
            temperature *= coolingDownFactor;
            return true;
        }
        else
        {
            double random = Math.random();
            double p = Math.exp(-(oldChromosomeFitness - newChromosomeFitness) / temperature);
            temperature *= coolingDownFactor;
            return random <= p;
        }
    }

    public static void SimulatedAnnealingChildrenPopulation(int simulatedAnnealingNumber, ArrayList<Chromosome> population, int temperature, double coolingDownFactor,
                                                            double exchangeRate, double reverseRate, double insertRate)
    {
        SimulatedAnnealing.temperature = temperature;
        SimulatedAnnealing.coolingDownFactor = coolingDownFactor;
        SimulatedAnnealing.exchangeRateBegin = 0;
        SimulatedAnnealing.exchangeRateEnd = SimulatedAnnealing.exchangeRateBegin + exchangeRate;
        SimulatedAnnealing.reverseRateBegin = SimulatedAnnealing.exchangeRateEnd;
        SimulatedAnnealing.reverseRateEnd = SimulatedAnnealing.reverseRateBegin + reverseRate;
        SimulatedAnnealing.insertRateBegin = SimulatedAnnealing.reverseRateEnd;
        SimulatedAnnealing.insertRateEnd = SimulatedAnnealing.insertRateBegin + insertRate;
        for(int i = 0; i < population.size(); i++)
        {
            for (int j = 0; j < simulatedAnnealingNumber; j++)
            {
                double random = Math.random();
                if (random >= SimulatedAnnealing.exchangeRateBegin && random < SimulatedAnnealing.exchangeRateEnd)
                {
                    Exchange(population.get(i));
                } else if (random >= SimulatedAnnealing.reverseRateBegin && random < SimulatedAnnealing.reverseRateEnd)
                {
                    Reverse(population.get(i));
                } else
                {
                    Insert(population.get(i));
                }
            }
        }
    }
}
